package org.pixelami.twisted.internet.defer
{
	import org.pixelami.twisted.errors.AlreadyCalledError;
	import org.pixelami.twisted.errors.AssertionError;
	import org.pixelami.twisted.failure.Failure;
	
	
	
	public class Deferred
	{
		private static var defID:uint = 0;
		
		
		public var callbacks:Array;
		
		public var called:Boolean;
	    public var paused:int;
	    public var timeoutCall:Object;
	    private var _debugInfo:Object;
	    
	    public var ref_id:uint;
	    
	    private var result:Object;
	
		/*
	    Are we currently running a user-installed callback?  Meant to prevent
	    recursive running of callbacks when a reentrant call to add a callback is
	    used.
	    */
	    private var _runningCallbacks:Boolean = false;
	
	    /*
	    Keep this class attribute for now, for compatibility with code that
	    sets it directly.
	    */
	    public var debug:Boolean = false
	    
	    
	    public static function succeed(...args):Deferred
		{
			var d:Deferred = new Deferred();
	    	d.callback.apply(null,args);
	    	return d;
		}
		
		public static function fail(...args):Failure
		{
			var f:Failure = new Failure();
	    	//f.callback.apply(null,args);
	    	return f;
		}
		
		
		
		public function Deferred()
		{
			callbacks = [];
			called = false;
	    	paused = 0;
	    	timeoutCall = null;
	    	_debugInfo = null;
	    	
	    	ref_id = defID++;
	    	//trace("new Deferred created ID : "+ref_id);
		}
		
		public function addCallbacks(_callback:Function, errback:Function=null, 
									callbackArgs:Array=null, callbackKeywords:Object=null,
	                     			errbackArgs:Array=null, errbackKeywords:Object=null):Deferred
		{
			/*
			Add a pair of callbacks (success and error) to this Deferred.

	        These will be executed when the 'master' callback is run.
	        */
	        //assert callable(callback)
	        //assert errback == None or callable(errback)
	        trace("add callbacks to Deferred id:"+ref_id+" = "+arguments)
	        var cbs:Array = [[_callback, callbackArgs, callbackKeywords],
	               [errback, errbackArgs, errbackKeywords]]
	        
	        this.callbacks.push(cbs)
	
	        if(called)
	            this._runCallbacks()
	        return this
		}
		
		public function addCallback(_callback:Function, ...args ):Deferred
		{
			/*
	        Convenience method for adding just a callback.
	
	        See L{addCallbacks}.
	        */
	        
	        //return this.addCallbacks(callback, ...args)
	        var _args:Array = [_callback,null,args];
	        return this.addCallbacks.apply(this,_args)
	    }
		
		public function addBoth(callback:Function, ...args):Deferred
		{
			return this.addCallbacks(callback, callback,
	                                 args, {},
	                                 args, {})
		}
		
		public function callback(result:Object):void
		{
			/*
			Run all success callbacks that have been added to this Deferred.

	        Each callback will have its result passed as the first
	        argument to the next; this way, the callbacks act as a
	        'processing chain'. Also, if the success-callback returns a Failure
	        or raises an Exception, processing will continue on the *error*-
	        callback chain.
	        */
			if((result is Deferred)) throw AssertionError("Deffered.callback result is Deferred");
        	this._startRunCallbacks(result);
		}
		
		public function errback(errback:Function, ...args):Deferred
		{
			
	        /*
	        Convenience method for adding just an errback.
	
	        See L{addCallbacks}.
	        */
	        var passthru:Function = function():void{};
	        return addCallbacks.apply(null,[passthru,errback,args]);
	        
		}
		
		
		private function _startRunCallbacks(_result:Object):void
		{
	        
	        if(called)
	        {
	            throw new AlreadyCalledError();
	        }
	        called = true;
	        this.result = _result;
	        if(timeoutCall)
	        {
	            try
	            {
	                this.timeoutCall.cancel()
	            }
	            catch(e:Error)
	            {
	                //pass
	            }
	            timeoutCall = null;
	        }
	        
	        this._runCallbacks();
	 	}
		
		private function _runCallbacks():void
		{
	        trace("_runCallbacks");
	        if(this._runningCallbacks)
	        {
	        	return;
	        }
	        
	        if(!paused)
	        {
	            //while(callbacks.length > 0)
	            //trace("callbacks.length at start of _runCallbacks = "+callbacks.length);
	            for(var i:uint = callbacks.length; i > 0;i--)
	            {
	                //var item:Array = callbacks.pop();
	                var item:Array = callbacks.shift();
	                ////trace("item = "+item);
	                //trace("callbacks.length = "+callbacks.length);
	                var fn_item:Array;
	                if(this.result is Failure)
	                {
	                	
	                	fn_item = item[1];
	                	//trace("Failure = "+fn_item);
	                }
	                else
	                {
	                	
	                	fn_item = item[0];	
	                	//trace("No Failure = "+fn_item);
	                }

	                var _callback:Function = fn_item[0];
	                trace("_callback - "+_callback);
	                var args:Array = fn_item[1] ? fn_item[1] : [];
	                trace("args - "+args);
	                var kw:Object = fn_item[2] ? fn_item[2] : {};
					//trace("kw - "+kw);
					//trace("pre try this.result = "+this.result);

	                try
	                {
	                    this._runningCallbacks = true;

	                    var _cbArgs:Array;
	                    _cbArgs = [this.result].concat(args);	
	                    trace("_cbArgs = "+_cbArgs);

	                    try
	                    {
		                    this.result = _callback.apply(null, _cbArgs);
		                    trace("this.result = "+this.result);
		                    if(result is Deferred)
		                    {
		                    	//trace("result Deferred id = "+result.ref_id);
		                    } 
		                    
		                }
		                catch(e:Error)
		                {
		                	trace("execute callback error"+e);
		                }
		                finally
		                {
		                	this._runningCallbacks = false;
		                }
	                    
	                    if(this.result is Deferred)
	                    {
	                        //trace("result is deferred - "+result.ref_id);

							/*
	                        # note: this will cause _runCallbacks to be called
	                        # "recursively" sometimes... this shouldn't cause any
	                        # problems, since all the state has been set back to
	                        # the way it's supposed to be, but it is useful to know
	                        # in case something goes wrong.  deferreds really ought
	                        # not to return themselves from their callbacks.
	                        */
	                        this.pause();
	                        this.result.addBoth(this._continue);
	                        break;
	                    }
	                }
	                catch(e:Error)
	                {
	                    //trace("callback Error - "+e);
	                    this.result = new Failure();
	                }
	            }
	        }
			/*
			TODO look at Twisted and implement debugInfo
	        if(this.result is Failure)
	        {
	            //trace("result is Failure");
	            this.result.cleanFailure();
	            
	            if(this._debugInfo == null)
	                this._debugInfo = DebugInfo();
	               
	            this._debugInfo.failResult = this.result;
	        }
	        else
	        {
	            //trace("whereis _debugInfo ?");
	            if(this._debugInfo != null)
	                this._debugInfo.failResult = null;
	        }
	        */
		}
		
		public function pause():void
		{
			//trace("pause - id:"+ref_id);
			this.paused ++;
			//trace("pause - value:"+paused);
			return;
		}
		
		public function unpause():void
		{
			//trace("unpause - id:"+ref_id);
			/*
			Process all callbacks made since L{pause}() was called.
	        */
	        this.paused --;
	        //trace("unpause - value:"+paused);
	        if (this.paused)
	        {
	        	//trace("paused is TRUE");
	        	return
	        }   
	        if (this.called)
	        {
	            this._runCallbacks()
	        }	        
		}
		
		public function _continue(_result:Object):void
		{
			//trace("_continue(_result) "+result);
			this.result = _result;
			
			unpause();
			
		}
		
	}
}